package com.guangying.util;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigInteger;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 
 * @author 光影序曲	QQ:150032099
 * @ClassName  Utils
 * @Description  tools class
 *                <br />
 *                [工具类]   
 * @date   2018年6月9日 下午5:57:09
 * @version V1.0
 */
public class Utils {
    
    /** [有界线程池] */
    public static final ThreadPoolExecutor THREAD_POOL
            = new ThreadPoolExecutor(2, 1024, 60L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(8192),
            new MyThreadFactory("MyThreadFactory"));
    
    /** [锁机制] */
    public static final ReentrantLock LOCK = new ReentrantLock();

    /** [UUID频道ID] */
    public static byte channelId;

    /** [任务调度线程池] */
    public static final ScheduledThreadPoolExecutor SCHEDULE_POOL 
        = new ScheduledThreadPoolExecutor(16, new MyScheduledThreadFactory("MyScheduledThreadFactory"));
    
    /** [JavaScript引擎] */
    public static final ScriptEngine SCRIPT_ENGINE = new ScriptEngineManager().getEngineByName("js");
    
    /** [解决同步问题的自增自减类对象] */
    public static final AtomicInteger INTEGER = new AtomicInteger();

    /** [1G容量] */
    private static final long MAX_FILE_LENGTH = 1024 << 20;

    /** [锁机制] */
    private static final Map<String, ReentrantLock> ALL_LOCK = new ConcurrentHashMap<>(1024);

    /** [XML数据超时, key = XML相对路径, value = 最后访问时间] */
    public static final Map<String, Long> ALL_LOCK_TIMEOUT = new ConcurrentHashMap<>(1024);

    /** [uuid16字符列表] */
    private static final String[] CHARS = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
    
    /**
     * 
     * @Description:  private constructor method
     *                <br />
     *                [私有化构造方法]
     */
    private Utils() {
        //设置任务调度线程池在调度完成后即便没有到底核心线程池大小也回收掉
        SCHEDULE_POOL.allowCoreThreadTimeOut(true);
        SCHEDULE_POOL.scheduleWithFixedDelay(() -> {
            try {
                long currentTimeMillis = System.currentTimeMillis();
                long maxTimeout = Duration.ofMinutes(5).toMillis();
                List<String> collect = ALL_LOCK_TIMEOUT.entrySet().stream()
                        .filter(e -> {
                            Long prevVisitTime = e.getValue();
                            return currentTimeMillis - prevVisitTime > maxTimeout;
                        })
                        .map(Map.Entry::getKey)
                        .collect(Collectors.toList());
                collect.forEach(k -> {
                    removeLock(k);
                    ALL_LOCK_TIMEOUT.remove(k);
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, 1L, 1L, TimeUnit.MINUTES);
    }
    
    /**
     * [公有静态方法]
     * ---------------------------------------------------------------------------------------
     */

    /**
     * [获取锁]
     * @param key
     * @return
     */
    public static ReentrantLock getLock(String key) {
        ALL_LOCK_TIMEOUT.put(key, System.currentTimeMillis());
        return ALL_LOCK.computeIfAbsent(key, k -> new ReentrantLock(true));
    }
    /**
     * [移除锁]
     * @param key
     * @return
     */
    public static ReentrantLock removeLock(String key) {
        return ALL_LOCK.remove(key);
    }

    /**
     * [获取带有长度的UTF8编码字符串二进制数据, 开头2字节为字符串长度]
     * @param str [要写入的字符串]
     * @return [二进制数据]
     */
    public static byte[] getUTF8(String str) {
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        int dataLen = bytes.length;
        ByteBuffer byteBuffer = ByteBuffer.allocate(2 + dataLen);
        byteBuffer.putShort((short) dataLen);
        byteBuffer.put(bytes);
        return byteBuffer.array();
    }

    /**
     * [访问URL并且以UTF-8获取结果]
     * @param url [网络链接地址]
     * @return [响应内容]
     * @throws IOException [当访问失败时抛出]
     */
    public static String getUrlResult(String url) throws IOException {
        return getUrlResult(url, 5000, (byte) 1);
    }

    /**
     * [访问URL并且以UTF-8获取结果]
     * @param url [网络链接地址]
     * @param timeout [超时时间, 单位毫秒]
     * @return [响应内容]
     * @throws IOException [当访问失败时抛出]
     */
    public static String getUrlResult(String url, int timeout, byte visitCount) throws IOException {
        if (visitCount > 5) {
            throw new IOException("visitCount过多");
        }
        URL u = new URL(url);
        HttpURLConnection connection = (HttpURLConnection)u.openConnection();
        connection.setConnectTimeout(timeout);
        connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
        int responseCode = connection.getResponseCode();
        boolean isRedirect = 301 == responseCode || 302 == responseCode;
        if (isRedirect) {
            String location = connection.getHeaderField("Location");
            return getUrlResult(location, timeout, ++visitCount);
        }

        //[字节流读取报错]
//        byte[] b = new byte[2048];
//        int len;
//        try (BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
//             ByteArrayOutputStream baos = new ByteArrayOutputStream(8192)) {
//            if (-1 != (len = bis.read(b))) {
//                baos.write(b, 0, len);
//            }
//            return baos.toString("UTF-8");
//        }
        //[必须用字符流读取才能正常解析]
        try (Reader reader = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)) {
//            int ch;
//            StringBuilder sb = new StringBuilder(8192);
//            while (-1 != (ch = reader.read())){
//                sb.append((char) ch);
//            }

//            try {
//                // 反射获取底层 Socket
//                Field httpField = connection.getClass().getDeclaredField("http");
//                httpField.setAccessible(true);
//                Object httpClient = httpField.get(connection);
//
//                Field socketField = httpClient.getClass().getSuperclass().getDeclaredField("serverSocket");
//                socketField.setAccessible(true);
//                Socket socket = (Socket) socketField.get(httpClient);
//
//                System.out.println("URL: " + url + " Local Port: " + socket.getLocalPort());
//            } catch (Exception e) {
//                e.printStackTrace();
//            }

            int len;
            StringBuilder sb = new StringBuilder(8192);
            char[] c = new char[2048];
            while (-1 != (len = reader.read(c))){
                sb.append(c, 0, len);
            }
            return sb.toString();
        }

        //[ArrayCopy会增加内存损耗, 严重可能爆内存]
//        try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
//            String s;
//            StringBuilder sb = new StringBuilder();
//            while ( null != (s = br.readLine()) ) {
//                sb.append(s);
//            }
//            return sb.toString();
//        }
    }

    /**
     * [访问URL并且返回字节数组]
     * @param url [网络链接地址]
     * @return [响应内容]
     * @throws IOException [当访问失败时抛出]
     */
    public static byte[] getNetByteArr(String url) throws IOException {
        URL u = new URL(url);
        URLConnection connection = u.openConnection();
        InputStream inputStream = connection.getInputStream();

        try (BufferedInputStream bis = new BufferedInputStream(inputStream);
             ByteArrayOutputStream baos = new ByteArrayOutputStream(8192)) {
            byte[] b = new byte[2048];
            int len;
            while ( -1 != (len = bis.read(b)) ) {
                baos.write(b, 0, len);
            }

            return baos.toByteArray();
        }
    }

    /**
     * [从本地读取内容并且返回字节数组]
     * @param path [本地地址]
     * @return [响应内容]
     * @throws IOException [当访问失败时抛出]
     */
    public static byte[] getLocalByteArr(String path) throws IOException {

        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
             ByteArrayOutputStream baos = new ByteArrayOutputStream(8192)) {
            byte[] b = new byte[2048];
            int len;
            while ( -1 != (len = bis.read(b)) ) {
                baos.write(b, 0, len);
            }

            return baos.toByteArray();
        }
    }

    /**
     * 
     * @Description:  close resultSet
     *                <br />
     *                [关闭结果集] 
     * @return: void    返回类型    
     * @param rs {ResultSet}
     * @date:   2018年6月9日 下午6:01:04 
     * @throws:
     */
    public static void closeResultSet(ResultSet rs) {
        if (null != rs) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 
     * @Description:  close prepared statement
     *                <br />
     *                [关闭预声明] 
     * @return: void    返回类型    
     * @date:   2018年6月9日 下午6:02:00
     * @throws:
     */
    public static void closeStatement(Statement s) {
        if (null != s) {
            try {
                s.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 
     *  
     * <br />
     * [关闭连接] 
     *
     * @return: void    返回类型    
     * @param s 连接对象
     * @date:   2018年8月9日 上午11:07:35 
     * @throws:
     */
    public static void closeSocket(Socket s) {
        if (null == s) {
            return;
        }
        try {
            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }
    
    /**
     * 
     * @Description:  close Reader
     *                <br />
     *                [关闭Reader] 
     * @return: void    返回类型    
     * @param r {Reader}
     * @date:   2018年6月9日 下午11:08:51 
     * @throws:
     */
    public static void closeReader(Reader r) {
        if (null == r) {
            return;
        }
        try {
            r.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 
     * @Description:  set auto commit
     *                <br />
     *                [设置事务是否自动提交] 
     * @return: void    返回类型    
     * @param con {Connection} 数据库连接
     * @param b {boolean} 是否自动提交
     * @date:   2018年6月10日 上午10:09:25 
     * @throws:
     */
    public static void setAutoCommit(Connection con, boolean b) {
        if (null != con) {
            try {
                con.setAutoCommit(b);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 
     * 
     * <br />
     * [关闭输出字符流] 
     *
     * @return: void    返回类型    
     * @param w 字符流对象
     * @date:   2018年7月31日 下午3:23:20 
     * @throws:
     */
    public static void closeWriter(Writer w) {
        if (null == w) {
            return;
        }
        try {
            w.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 
     * @Description:  affair rollback
     *                <br />
     *                [事务回滚] 
     * @return: void    返回类型    
     * @param con {Connection} 数据库连接
     * @date:   2018年6月10日 上午10:11:10 
     * @throws:
     */
    public static void rollback(Connection con) {
        if (null != con) {
            try {
                con.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }
    }
    
    /**
     * 
     * <br />
     * [弹出一个对话框] 
     *
     * @return: void    返回类型    
     * @param content 内容
     * @date:   2018年7月8日 下午8:43:45 
     * @throws:
     */
    public static void showMessageDialog(String content) {
        showMessageDialog(content, "提示");
    }
    
    /**
     * 
     * 
     * <br />
     * [非阻塞的弹出对话框] 
     *
     * @return: void    返回类型    
     * @param content 内容
     * @date:   2018年8月14日 下午3:38:05 
     * @throws:
     */
    public static void showUnblockMessageDialog(final String content) {
        THREAD_POOL.execute(new Runnable() {

            @Override
            public void run() {
                showMessageDialog(content, "提示");
            }
            
        });
        
    }
    
    /**
     * 
     * <br />
     * [弹出一个对话框] 
     *
     * @return: void    返回类型    
     * @param content 内容
     * @param title 标题
     * @date:   2018年7月8日 下午8:15:40 
     * @throws:
     */
    public static void showMessageDialog(String content, String title) {
        JOptionPane.showMessageDialog(null, content, title, JOptionPane.OK_OPTION);
    }
    

    
    /**
     * 
     * <br />
     * [判断文本框是否为空字符串] 
     *
     * @return: boolean    返回类型    
     * @param t1 不定参 文本框对象
     * @return 
     * @date:   2018年7月8日 下午8:40:46 
     * @throws:
     */
    public static boolean isEmptyTextField(JTextField... t1) {
        
        //默认文本框不为空
        boolean isEmpty = false;
        for (JTextField jTextField : t1) {
            
            //当为null时直接返回true
            if (jTextField.getText() == null) {
                isEmpty = true;
                break;
            } 
            
            //如果是空字符串也返回true
            if (jTextField.getText().trim().isEmpty()) {
                isEmpty = true;
                break;
            }
        }
        return isEmpty;
    }
    
    /**
     * 
     * @Description:  set to crystal JFrame
     *                <br />
     *                [设置为水晶界面] 
     * @return: void    返回类型     
     * @date:   2018年6月3日 上午11:03:50 
     * @throws:
     */
    public static void setNimbus() {
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }

    /**
     * 
     * 
     * <br />
     * [关闭文件读取] 
     *
     * @return: void    返回类型    
     * @param raf 
     * @date:   2018年7月14日 下午5:21:01 
     * @throws:
     */
    public static void closeRandomAccessFile(RandomAccessFile raf) {
        if (null == raf) {
            return;
        }
        try {
            raf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 
     * 
     * <br />
     * [关闭通道]
     *
     * @return: void 返回类型
     * @param ch
     *            通道对象
     * @date: 2018年7月23日 下午9:15:03
     * @throws:
     */
    public static void closeChannel(Channel ch) {
        if (null == ch) {
            return;
        }
        try {
            ch.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    /**
     * 
     * 
     * <br />
     * [关闭输入流] 
     *
     * @return: void    返回类型    
     * @param in 输入流对象
     * @date:   2018年7月30日 下午8:43:33 
     * @throws:
     */
    public static void closeInputStream(InputStream in) {
        if (null == in) {
            return;
        }
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 
     * 
     * <br />
     * [关闭输入流] 
     *
     * @return: void    返回类型    
     * @date:   2018年7月30日 下午8:43:33
     * @throws:
     */
    public static void closeOutputStream(OutputStream out) {
        if (null == out) {
            return;
        }
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 
     * 
     * <br />
     * [生成文件的MD5校验码] 
     *
     * @return: String    返回类型    
     * @param path 文件路径
     * @return 
     * @date:   2018年7月30日 下午8:43:51 
     * @throws:
     */
    public static String getMD5Three(String path) {

        final File f = new File(path);
        
        //[如果文件不存在直接返回null]
        if (!f.exists()) {
            return null;
        }

        final String md5Name = "MD5";
        
        //每次处理的文件长度 10M
        final int everyLength = 50 * (1024 << 10);
        
        //文件长度
        final long length = f.length();
        
        //字符数组长度
        long arrLength = length / everyLength;
        if (arrLength > 0) {
            
            //字符数组长度是否需要+1
            boolean isInc = length % everyLength > 0;
            if (isInc) {
                arrLength ++;
            }
            
            
            class MyCallable implements Callable<String>, Comparable<MyCallable> {

                private int index;
                
                
                private volatile String md5;
                
                public MyCallable(int index) {
                    this.index = index;
                }

                @Override
                public String call() throws Exception {
                    MessageDigest md = MessageDigest.getInstance(md5Name);
                    RandomAccessFile raf = null;
                    FileChannel fc = null;
                    try {
                        raf = new RandomAccessFile(f, "r");
                        fc = raf.getChannel();
                        int newPosition = index * everyLength;
                        int limitPosition = newPosition + everyLength;
                        int num;
                        fc.position(newPosition);
                        final int capacity = 1024 << 10;
                        ByteBuffer bb = ByteBuffer.allocate(capacity);
                        if ((newPosition + everyLength) < f.length()) {
                            while (fc.position() < limitPosition) {
                                while (bb.remaining() > 0) {
                                    num = fc.read(bb);
                                    if (num == -1) {
                                        break;
                                    }
                                    fc.position(num + fc.position());
                                }
                                bb.flip();
                                md.update(bb.array(), bb.position(), bb.limit());
                                bb.clear();
                            }
                        } else {
                            while (fc.position() < length) {
                                while (bb.remaining() > 0) {
                                    num = fc.read(bb);
                                    if (num == -1) {
                                        break;
                                    }
                                    fc.position(num + fc.position());
                                }
                                bb.flip();
                                md.update(bb.array(), bb.position(), bb.limit());
                                bb.clear();
                            }
                        }
                        byte[] b = md.digest();
                        md5 = new BigInteger(1, b).toString(16);
                    } finally {
                        closeChannel(fc);
                        closeRandomAccessFile(raf);
                    }
                    return md5;
                }

                @Override
                public int compareTo(MyCallable o) {
                    
                    return this.index - o.index;
                }
                
                @Override
                public String toString() {
                    return md5;
                }
                
            }
            
            Set<MyCallable> md5List = new TreeSet<>();
            
            for (int i = 0; i < arrLength; i++) {
                md5List.add(new MyCallable(i));
            }


            StringBuilder sb = new StringBuilder();
            try {
                
                List<Future<String>> fu = THREAD_POOL.invokeAll(md5List);
                for (Future<String> future : fu) {
                    sb.append(future.get());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            
            return sb.toString();
        } else {

            byte[] buffer = new byte[1024 << 10];
            int len = 0;
            BigInteger bi = null;
            BufferedInputStream bis = null;
            try {
                MessageDigest md = MessageDigest.getInstance(md5Name);
                bis = new BufferedInputStream(new FileInputStream(f));
                while ((len = bis.read(buffer)) != -1) {
                    md.update(buffer, 0, len);
                }
                
                byte[] b = md.digest();
                 bi = new BigInteger(1, b);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                closeInputStream(bis);
            }
            return bi.toString(16);
        }
    }

    /**
     * [重载方法]
     * @return: String    返回类型
     * @param f 文件对象
     * @date:   2018年7月30日 下午8:51:05
     */
    public static String getMD5Three(File f) {
        return getMD5Three(f.getAbsolutePath());
    }

    /**
     * [获取MD5字符串]
     * @return: [MD5字符串]
     * @param in [文件流]
     */
    public static String getMD5Three(InputStream in) throws IOException, NoSuchAlgorithmException {
        byte[] buffer = new byte[1024];
        int len;
        try (BufferedInputStream bis = new BufferedInputStream(in)) {
            MessageDigest md = MessageDigest.getInstance("MD5");
            while ((len = bis.read(buffer)) != -1) {
                md.update(buffer, 0, len);
            }

            byte[] b = md.digest();
            BigInteger bi = new BigInteger(1, b);
            return bi.toString(16);
        }
    }

    /**
     * [重载的链接方法，将错误信息打印输出到日志文件中]
     * @param path 日志文件路径
     * @param t 异常对象
     * @date:   2018年7月31日 下午3:28:22 
     */
    public static void printLogFile(String path, Throwable t) {
    	printLogFile(path, t, null);
    }
    
    /**
     * [重载的链接方法，将自定义信息打印输出到日志文件中]
     * @return: void    返回类型
     * @param path 日志文件路径
     * @param content 要写入的内容
     * @date:   2018年8月8日 上午9:20:43 
     */
    public static void printLogFile(String path, String content) {
    	printLogFile(path, null, content);
    }
    
    /**
     * [重载的核心主方法]
     * @param path 日志文件路径
     * @param t 异常对象
     * @param content  要写入的内容
     * @date:   2018年8月8日 上午9:20:46 
     */
    public static void printLogFile(String path, Throwable t, String content) {
    	PrintWriter pw = null;
        File f = new File(path);
        
        //如果文件大于1G那么控制台进行提示
        if (f.length() > MAX_FILE_LENGTH) {
            System.out.println(path + "已经超过1G容量，只有清理后才能再写入日志信息");
            return;
        }
        
        if (!f.getParentFile().exists()) {
            f.getParentFile().mkdirs();
        }
        try {
            pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f, true), "UTF-8"));
            pw.println();
            pw.println("----------------------------------" 
                + DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date()) 
                + "------------------------------------------");
            if (content != null) {
            	pw.println(content);
            }
            if (t != null) {
            	t.printStackTrace(pw);
            }
            pw.println();
            
            pw.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } finally {
            closeWriter(pw);
        }
    }

    
    /**
     * 
     *  
     * <br />
     * [将对象写出去，最后重置对象输出流] 
     *
     * @return: void    返回类型    
     * @param oos 对象输出流
     * @param o 要写出的对象
     * @date:   2018年8月9日 上午10:20:11 
     * @throws:
     */
    public static void writeObject(ObjectOutputStream oos, Object o) {
        try {
            oos.writeObject(o);
            oos.reset();
        } catch (IOException e) {
            e.printStackTrace();
            Utils.printLogFile("log/" + Utils.class.getName() + ".log", e);
        }
    }

    
    /**
     * 
     * 
     * <br />
     * [退出登录器JVM虚拟机] 
     *
     * @return: void    返回类型    
     * @param delay 延迟退出时间
     * @date:   2018年8月17日 上午10:08:13 
     * @throws:
     */
    public static void exit(long delay) {
        SCHEDULE_POOL.schedule(new Runnable() {

            @Override
            public void run() {
                //退出登录器JVM虚拟机
                System.exit(0);
            }
            
        }, delay, TimeUnit.SECONDS);
    }

    
    /**
     * 
     * 
     * <br />
     * [获取电脑的MAC地址] 
     *
     * @return: String    返回类型    
     * @return 
     * @date:   2018年8月27日 下午7:22:59 
     * @throws:
     */
    public static String getMac() {
        try {
            //[准备工作，获取到MAC字节数组]
            final InetAddress address = InetAddress.getLocalHost();
            byte[] b = NetworkInterface.getByInetAddress(address).getHardwareAddress();
            
            //[将字节数组转为16进制字符串]
            StringBuilder sb = new StringBuilder();
            String hex;
            for (int i = 0, len = b.length; i < len; i ++) {
                
                //[添加分隔符]
                if (i != 0) {
                    sb.append("-");
                }
                
                hex = Integer.toHexString(b[i] & 0xFF);
                
                //[如果字符串长度为1那么补一个0在前面]
                if (hex.length() == 1) {
                    hex = "0" + hex;
                }
                
                sb.append(hex);
            }
            
            return sb.toString().toUpperCase();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 
     * 
     * <br />
     * [添加数据长度到数据头信息] 
     *
     * @return: B    返回类型    
     * @param obj [只能是ByteBuffer或者byte[]的实例,否则报错]
     * @return 
     * @date:   2019年2月15日 下午3:20:39 
     * @throws:
     */
    @SuppressWarnings("unchecked")
    public static <B> B addDataLengthToDataPackageHeader(B obj) {

        final int headSize = 4;
        if (obj instanceof ByteBuffer) {
            ByteBuffer buffer = (ByteBuffer) obj;
            byte[] b = buffer.array();
            final int capacity = headSize + b.length;
            ByteBuffer bb = ByteBuffer.allocate(capacity);
            bb.putInt(capacity).put(b);
            return (B) bb;
        }
        
        if (obj instanceof byte[]) {
            byte[] b = (byte[]) obj;
            final int capacity = headSize + b.length;
            ByteBuffer bb = ByteBuffer.allocate(capacity);
            bb.putInt(capacity).put(b);
            return (B) bb.array();
        }
        
        throw new IllegalArgumentException("参数类型错误,传入的参数不上ByteBuffer对象或者byte[].");
    }
    
    /**
     * 
     * @Description:    绘制图片   
     * @param image 原图片
     * @param g 画笔
     * @param x 图片开始的x坐标
     * @param y  图片开始的y坐标
     * @return void    返回类型  
     * @throws  
     * @date:   2018年2月24日 下午10:54:28
     */
    public static void paintImage(Image image, Graphics g, int x, int y) {
        BufferedImage bi = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = bi.createGraphics();
        /*if (fl) {
            ((Graphics2D) g).setComposite(AlphaComposite.SrcOver.derive(MyJPanel.z/10000.0f));
        }*/
        g2d.drawImage(image, 0, 0, null);
        g.drawImage(bi, x, y, null);
        g2d.dispose();
    }
    
    /**
     * 
     * 
     * <br />
     * [对数据进行GZIP压缩] 
     *
     * @return: byte[]    返回类型    [压缩后的字节数组]
     * @param original [源字节数组]
     * @return 
     * @date:   2019年2月19日 上午10:23:36 
     * @throws:
     */
    public static byte[] gzipCompress(byte[] original) {
        
        int size = 1024;
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
        GZIPOutputStream gzOut = null;
        try {
            gzOut = new GZIPOutputStream(baos);
            gzOut.write(original);
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeOutputStream(gzOut);
        }
        
        return null;
    }
    
    /**
     * 
     * 
     * <br />
     * [对数据进行GZIP解压缩] 
     *
     * @return: byte[]    返回类型    [解压缩后的字节数组]
     * @param compressedByteArray [已压缩的字节数组]
     * @return 
     * @date:   2019年2月19日 下午4:07:36 
     * @throws:
     */
    public static byte[] gzipUncompress(byte[] compressedByteArray) {

        int size = 1024;
        
        ByteArrayInputStream bais = new ByteArrayInputStream(compressedByteArray);
        ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
        
        GZIPInputStream gzIn = null;
        try {
            gzIn = new GZIPInputStream(bais);
            int len;
            byte[] b = new byte[size];
            
            while ((len = gzIn.read(b)) != -1) {
                baos.write(b, 0, len);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeInputStream(gzIn);
        }
        
        return null;
    }
    
    /**
     * 
     * 
     * <br />
     * [获取压缩后并且已经添加头信息(字节长度)的字节数组] 
     *
     * @return: byte[]    返回类型    [已经压缩后并且已经添加头信息(字节长度)的字节数组]
     * @param original [未经过任何处理的字节数组]
     * @return 
     * @date:   2019年2月19日 下午4:14:00 
     * @throws:
     */
    public static byte[] getAddLengthAndCompressedByteArray(byte[] original) {
        return addDataLengthToDataPackageHeader(gzipCompress(original));
    }
    
    /**
     * 
     * 
     * <br />
     * [压缩文件并返回被压缩文件的字节数组,支持文件夹压缩] 
     *
     * @return: byte[]    返回类型    [被压缩文件的字节数组]
     * @param path [文件路径]
     * @return
     * @throws IOException 
     * @date:   2019年3月7日 上午10:32:18 
     * @throws:
     */
    public static byte[] zipCompress(String path) throws IOException {
        return zipCompress(new File(path));
    }
    
    /**
     * 
     * 
     * <br />
     * [压缩文件并返回被压缩文件的字节数组,支持文件夹压缩] 
     *
     * @return: byte[]    返回类型     [被压缩文件的字节数组]
     * @param sourceFile [源文件对象]
     * @return
     * @throws IOException 
     * @date:   2019年3月7日 上午10:33:43 
     * @throws:
     */
    public static byte[] zipCompress(File sourceFile) throws IOException {
        System.out.println("开始ZIP压缩文件");
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
        BufferedOutputStream bos = null;
        try {
            ZipOutputStream zos = new ZipOutputStream(baos);
            bos = new BufferedOutputStream(zos);
            String parentPath = sourceFile.getName();
            zipCompressRecursion(zos, bos, sourceFile, parentPath);
        } finally {
            closeOutputStream(bos);
        }
        
        System.out.println("ZIP压缩完成");
        return baos.toByteArray();
    }

    /**
     * [SHA1摘要算法] 
     * @return: String    返回类型    [SHA1字符串]
     * @param str [要加密的字符串]
     * @date:   2019年3月8日 下午3:33:33 
     */
    public static String sha1(String str) {
        return messageDigest(str, "SHA1");
    }

    /**
     * [SHA256摘要算法]
     * @return: String    返回类型    [SHA256字符串]
     * @param str [要加密的字符串]
     * @date:   2025年8月25日 下午3:33:33
     */
    public static String sha256(String str) {
        return messageDigest(str, "SHA-256");
    }
    
    /**
     * [MD5摘要算法] 
     * @author 光影九曜	QQ:150032099
     * @return: String    返回类型    
     * @param str [要加密的字符串]
     * @date:   2019年11月21日 上午10:44:56 
     */
    public static String md5(String str) {
        return messageDigest(str, "MD5");
    }

    /**
     * [判断是否为64位操作系统]
     * @return [当且仅当 当前系统为64位操作系统时返回true]
     * @throws IOException [指令执行失败抛出异常]
     */
    public static boolean is64Windows() throws IOException {
        String cmd = "systeminfo";
        ProcessBuilder pb = new ProcessBuilder();
        Process process = pb.redirectErrorStream(true).command(cmd.split(" ")).start();

        try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"))) {
            String s;
            while (Objects.nonNull(s = br.readLine())) {
                if (s.contains("64-based")) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * [去掉横杠后的UUID]
     * @return [32位字符串]
     */
    public static String uuid() {
        //[固定32位]
//        return UUID.randomUUID().toString().replace("-", "");
        //[可能30, 31, 32位, 但是不会超过32位]
//        return md5(UUID.randomUUID().toString() + channelId);
        return uuid22();
    }

    /**
     * [去掉横杠后的UUID]
     * @return [32位字符串]
     */
    public static String uuid(int i) {
//        return md5(UUID.randomUUID().toString() + i);
        return uuid22();
    }

    /**
     * [去掉横杠后的UUID]
     * @return [32位字符串]
     */
    public static String uuid(String i) {
//        return md5(UUID.randomUUID().toString() + i);
        return uuid22();
    }

    public static String uuid16() {
        StringBuilder sb = new StringBuilder();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        for (int i = 0; i < 16; i++) {
            String segment = uuid.substring(i * 2, i * 2 + 2);
            int num = Integer.parseInt(segment, 16);
            sb.append(CHARS[num % CHARS.length]);
        }
        return sb.toString();
    }

    public static String uuid22() {
        UUID uuid = UUID.randomUUID();
        byte[] bytes = new byte[16];
        System.arraycopy(
                longToBytes(uuid.getMostSignificantBits()), 0, bytes, 0, 8
        );
        System.arraycopy(
                longToBytes(uuid.getLeastSignificantBits()), 0, bytes, 8, 8
        );
        return Base64.getUrlEncoder()
                .withoutPadding()
                .encodeToString(bytes)
                .replaceAll("[^a-zA-Z0-9]", "");
    }

    private static byte[] longToBytes(long value) {
        byte[] result = new byte[8];
        for (int i = 7; i >= 0; i--) {
            result[i] = (byte)(value & 0xFF);
            value >>= 8;
        }
        return result;
    }

    /**
     * [日期授权]
     * @param year [过期年]
     * @param month [过期月]
     * @param dayOfMonth [过期日]
     * @return [当且仅当过期日期 < 当前日期 返回true, 说明已经过期]
     * @throws IOException
     */
    public static boolean authorization(int year, int month, int dayOfMonth) throws IOException {
        URL url = new URL("https://www.baidu.com/");
        URLConnection uc = url.openConnection();
        uc.connect();
        long ld = uc.getDate();
        Date date = new Date(ld);
        LocalDate expiredDate = LocalDate.of(year, month, dayOfMonth);
        LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        LocalDate baiduServerDate = localDateTime.toLocalDate();
        //[如果过期时间小于百度时间说明已过期]
        return expiredDate.compareTo(baiduServerDate) < 0;
    }

    /**
     * @return T
     * @Description <深度克隆对象，对象需要序列化>
     * @Author Zhaiyt
     * @Date 14:11 2018/12/4
     * @Param [obj]
     **/
    public static <T extends Serializable> T clone(T obj) throws IOException, ClassNotFoundException {
        //将对象读取到内存中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
        outputStream.writeObject(obj);
        //从内存中读取
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return (T) objectInputStream.readObject();
    }

    /**
     * [私有静态方法]
     * -----------------------------------------------------------------------------------------
     */
    

    /**
     * 
     * <br />
     * [摘要加密算法] 
     *
     * @return: String    返回类型    
     * @param str [要加密的字符串]
     * @param algorithm [摘要算法名称]
     * @return 
     * @date:   2019年3月8日 下午3:42:04 
     * @throws:NullPointerException [当要加密的字符串为null或者长度为0时抛出次异常]
     */
    private static String messageDigest(String str, String algorithm) {
        if (null == str) {
            throw new NullPointerException("加密字符串为null");
        }
        if (0 == str.length()) {
            throw new NullPointerException("加密字符串为空字符串,长度为0");
        }
        
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            md.update(str.getBytes("UTF-8"));
            
            byte[] b = md.digest();
            BigInteger bi = new BigInteger(1, b);
            
            return bi.toString(16);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("未知异常");
    }

    /**
     * 
     * 
     * <br />
     * [压缩文件的递归方法,将压缩的数据写入流中] 
     *
     * @return: void    返回类型    
     * @param zos [zip输出流]
     * @param bos [缓冲输出流,包含zos]
     * @param sourceFile [源文件对象]
     * @param parentPath [源文件为文件夹时表示的是父路径,源文件为单个文件时表示的是子路径,不需要再加分割符和源文件名]
     * @throws IOException 
     * @date:   2019年3月7日 上午10:30:16 
     * @throws:
     */
    private static void zipCompressRecursion(ZipOutputStream zos, BufferedOutputStream bos, File sourceFile, String parentPath) throws IOException {
        System.out.println(sourceFile.getPath());
        //[判断文件是否为目录]
        if (sourceFile.isDirectory()) {
            //[获取子文件列表]
            File[] fileChildArray = sourceFile.listFiles();
            
            //[判断子文件列表中的数量是否为0, 如果为0则说明是一个空文件夹,直接添加条目不写入数据]
            if (0 == fileChildArray.length) {
                //[这里如果不加分割符的话或默认为添加文件而不是文件夹,分割符不能是File.separator,否则空文件夹同样解析为了文件]
                zos.putNextEntry(new ZipEntry(parentPath + "/"));
                return;
            }
            
            //[如果子文件列表中的数量不为0,那么说明有子文件,遍历所有的子文件然后递归调用本方法]
            for (File file : fileChildArray) {
                zipCompressRecursion(zos, bos, file, parentPath + "/" + file.getName());
            }
            return;
        }
        
        //[不是目录直接添加条目然后写入数据]
        zos.putNextEntry(new ZipEntry(parentPath));
        BufferedInputStream bis = null;
        try {
            bis = new BufferedInputStream(new FileInputStream(sourceFile));
            byte[] b = new byte[4096];
            int len;
            while ( (len = bis.read(b)) != -1 ) {
                bos.write(b, 0, len);
            }
            bos.flush();
        } finally {
            closeInputStream(bis);
        }
    }
    
    /**
     * [私有静态内部类]
     * -----------------------------------------------------------------------------------------
     */
    
    
    /**
     * 
     * @author 光影序曲 QQ:150032099
     * @ClassName:  MyThreadFactory
     * @Description:  MyThreadFactory
     *                <br />
     *                [自定义线程池工厂]   
     * @date:   2018年6月17日 下午7:05:08 
     * @version V1.0
     */
    public static class MyThreadFactory implements ThreadFactory {

        /** [下标索引] */
        protected int i = 0;
        
        /** [线程工厂名] */
        protected String name;
        
        public MyThreadFactory(String name) {
            this.name = name;
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName(name + " - " + ++i);
            return t;
        }

    }
    

    /**
     * 
     * @author 光影序曲 QQ:150032099
     * @ClassName:  MyScheduledThreadFactory
     * @Description:  custom thread pool factory
     *                <br />
     *                [计划调度类的线程工厂]   
     * @date:   2018年6月9日 下午10:35:04 
     * @version V1.0
     */
    private static class MyScheduledThreadFactory extends MyThreadFactory {

        public MyScheduledThreadFactory(String name) {
            super(name);
        }
    }

    /**
     * [加密]
     * @param str [要加密的字符串]
     * @param key [自定义密钥]
     * @return [base64编码的加密后字符串]
     */
    public static String encrypt(String str, String key) {
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        byte[] k = key.getBytes(StandardCharsets.UTF_8);
        for (int i = 0, j = 0, len = bytes.length, keyLen = k.length; i < len; i++, j++) {
            if (j >= keyLen) {
                j = 0;
            }
            bytes[i] ^= k[j];
        }
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * [解密]
     * @param str [base64编码的加密后字符串]
     * @param key [自定义密钥]
     * @return [解密后的字符串]
     */
    public static String decrypt(String str, String key) {
        byte[] bytes = Base64.getDecoder().decode(str);
        byte[] k = key.getBytes(StandardCharsets.UTF_8);
        for (int i = 0, j = 0, len = bytes.length, keyLen = k.length; i < len; i++, j++) {
            if (j >= keyLen) {
                j = 0;
            }
            bytes[i] ^= k[j];
        }
        return new String(bytes, StandardCharsets.UTF_8);
    }
}
